/**
 ****************************************************************************************************
 * @file        main.c
 * @author      xulida
 * @version     V1.0
 * @date        2025-08-17
 * @brief       我的小智
 * @license     Copyright (c) 2020-2032
 ****************************************************************************************************
*/

#include <stdio.h>
#include <string.h>
#include "cJSON.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "led.h"
#include "spi.h"
#include "iic.h"
// #include "lcd.h"
#include "xl9555.h"
// #include "fonts.h"
#include "spi_sdcard.h"
// #include "text.h"
#include "exfuns.h"
#include "es8388.h"
#include "audioplay.h"
#include "recorder.h"
#include "i2s.h"
#include "led.h"

#include "wifi_config.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "app_sr.h"
#include "esp_websocket_client.h"

#include "mbedtls/hmac_drbg.h"
#include "mbedtls/md.h"
#include "mbedtls/base64.h" 
#include "esp_sntp.h"
#include <time.h>
#include <ctype.h>

// 讯飞鉴权参数
#define API_KEY "04fc2f1d32dd95dfda11fffc3c1ebafd"
#define API_SECRET "MDQ0MjliOTc5NmJjNDQ0ODljYTg5YzU5"
#define WEBSOCKET_URI "wss://sparkos.xfyun.cn/v1/openapi/chat"

i2c_obj_t i2c0_master;
esp_websocket_client_handle_t client = NULL;


// 音频配置参数（根据实际硬件调整）
#define AUDIO_FRAME_SIZE  1280  // 每帧音频数据大小（PCM）
#define QUEUE_LEN         20    // 队列长度（缓存20帧数据）
#define JSON_BUF_SIZE     (AUDIO_FRAME_SIZE * 2)  // Base64编码后长度约为原长度的4/3，预留冗余

// 录制发送队列：**录制任务**完成后编码成base64数据塞入**录制发送队列**，**发送任务**从队列中取出base64任务组装成json发送出去，2tasks共用1核
typedef struct {
    unsigned char base64_str[JSON_BUF_SIZE];  // 存储带Base64的JSON数据
    size_t json_len;           // JSON字符串长度
} audio_json_t;

// 接收播放队列：socket回调函数接收数据之后取出其中的base64数据，塞入**接收播放队列**，播放任务取出播放
typedef struct {
    unsigned char json_str[JSON_BUF_SIZE];  // 存储带Base64的JSON数据
    size_t json_len;           // JSON字符串长度
} data_json_t;

// 全局队列和信号量
QueueHandle_t g_audio_queue = NULL;           // 录制发送队列
QueueHandle_t g_data_queue = NULL;           // 接收播放队列
SemaphoreHandle_t g_i2s_sem = NULL;           // I2S硬件访问信号量


static void record_task(void *arg) ;
static void play_task(void *arg);

// HMAC-SHA256计算函数
static int hmac_sha256(const unsigned char *key, size_t key_len, const unsigned char *input, size_t input_len, unsigned char *output)
{
    mbedtls_md_context_t ctx;
    mbedtls_md_init(&ctx);
    int ret = mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
    if (ret != 0) return ret;
    ret = mbedtls_md_hmac_starts(&ctx, key, key_len);
    if (ret != 0) { mbedtls_md_free(&ctx); return ret; }
    ret = mbedtls_md_hmac_update(&ctx, input, input_len);
    if (ret != 0) { mbedtls_md_free(&ctx); return ret; }
    mbedtls_md_hmac_finish(&ctx, output);
    mbedtls_md_free(&ctx);
    return 0;
}

static char* get_rfc1123_date(void)
{
    time_t now;
    time(&now);
    struct tm *gmt_time = gmtime(&now); // 获取GMT时区时间
    static char date_str[30];
    // 格式: Wed, 10 Jul 2019 07:35:43 GMT
    strftime(date_str, sizeof(date_str), "%a, %d %b %Y %H:%M:%S GMT", gmt_time);
    return date_str;
}

// URL编码函数
static char* url_encode(const char* str) {
    static char encoded[128];
    int pos = 0;
    for (int i = 0; str[i] != '\0'; i++) {
        if (isalnum((unsigned char)str[i]) || str[i] == '-' || str[i] == '_' || str[i] == '.' || str[i] == '~') {
            encoded[pos++] = str[i];
        } else {
            pos += sprintf(encoded + pos, "%%%02X", (unsigned char)str[i]);
        }
    }
    encoded[pos] = '\0';
    return encoded;
}

// 生成讯飞WebSocket鉴权URL
static char* generate_auth_url(void)
{
    const char* date_str = get_rfc1123_date(); // 使用RFC1123格式日期
    printf("date_str: %s\n", date_str);
    char nonce[17];
    esp_fill_random(nonce, 16);
    nonce[16] = '\0';

    // 拼接签名原文
    char signature_origin[128];
    sprintf(signature_origin, "host: sparkos.xfyun.cn\ndate: %s\nGET /v1/openapi/chat HTTP/1.1", date_str);
    printf("signature_origin: %s\n", signature_origin);

    // 计算HMAC-SHA256
    unsigned char signature[32];
    int hmac_ret = hmac_sha256((unsigned char*)API_SECRET, strlen(API_SECRET),
               (unsigned char*)signature_origin, strlen(signature_origin), signature);
    printf("signature HMAC-SHA256: %s\n", signature);

    // Base64编码
    char signature_b64[64];
    size_t output_len;
    int b64_len = mbedtls_base64_encode((unsigned char*)signature_b64, sizeof(signature_b64), &output_len,
                                        signature, 32);
    printf("signature_base64: %s\n", signature_b64);

    // 拼接authorization_origin
    char authorization_origin[256];
    sprintf(authorization_origin,"api_key=\"%s\", algorithm=\"hmac-sha256\", headers=\"host date request-line\", signature=\"%s\"",
             API_KEY, signature_b64);
    printf("authorization_origin: %s\n", authorization_origin);

    // 二次Base64编码(对authorization_origin)
    char authorization_b64[512];
    size_t auth_output_len;
    b64_len = mbedtls_base64_encode((unsigned char*)authorization_b64, sizeof(authorization_b64), &auth_output_len,
                                   (unsigned char*)authorization_origin, strlen(authorization_origin));
    printf("authorization_base64: %s\n", authorization_b64);

    // 构建完整URL
    static char auth_url[768];
    char* encoded_date = url_encode(date_str);
    sprintf(auth_url, "%s?authorization=%s&date=%s&host=sparkos.xfyun.cn",
            WEBSOCKET_URI, authorization_b64, encoded_date);
    printf("auth_url: %s\n", auth_url);

    return auth_url;
}

static void time_sync_notification_cb(struct timeval *tv)
{
    ESP_LOGI("NTP", "时间同步完成");
}

// 初始化NTP时间同步
static void ntp_init(void)
{
    esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
    esp_sntp_setservername(0, "pool.ntp.org");
    sntp_set_time_sync_notification_cb(time_sync_notification_cb);
    esp_sntp_init();

    // 等待时间同步
    time_t now = 0;
    struct tm timeinfo = { 0 };
    int retry = 0;
    const int retry_count = 20;
    while (timeinfo.tm_year < (2020 - 1900) && ++retry < retry_count) {
        ESP_LOGI("NTP", "等待时间同步...");
        vTaskDelay(1000 / portTICK_PERIOD_MS);
        time(&now);
        localtime_r(&now, &timeinfo);
    }
}

// 持续发送音频数据的任务
// 数据格式
/**
 * 双工模式(continuous)：应用于持续上传音频的模式，类似语音通话的场景
    header.stmid的值【固定不变】
    header.status： 会话的状态。第一个数据包的header.status是0，后续都是1，结束时传2。
    payload.audio.status：音频的状态。第一个数据包的header.status是0，后续都是1，结束时传2。
    第一个数据包，发送全部的字段。后续的数据包，可以发送简化的内容（可以不发parameter部分，header中也只需要发送几个关键的字段）。
    【持续不断的发送音频】：即使用户没有说话，也要一直发送音频。服务侧会自动判断用户有没有说话。
*/
/**
 * 建立ws连接
# 第一个数据包：需要发送全部的字段  (header.status=0, payload.audio.status=0)
{"header":{"app_id":"879230fc","uid":"thzhang4","status":0,"stmid":"0","scene":"ai-personality-1","os_sys":"android","interact_mode":"continuous","test":"孙悟空","pers_param":"{\"appid\":\"879230fc\",\"uid\":\"thzhang4\"}"},"parameter":{"iat":{"vgap":60,"dwa":"wpgs","iat":{"encoding":"utf8","compress":"raw","format":"json"}},"nlp":{"nlp":{"encoding":"utf8","compress":"raw","format":"json"},"new_session":"false"},"tts":{"vcn":"x5_lingxiaoyue_flow","speed":50,"volume":50,"pitch":50,"tts":{"encoding":"raw","sample_rate":16000,"channels":1,"bit_depth":16}}},"payload":{"audio":{"status":0,"audio":"base64的音频","encoding":"raw","sample_rate":16000,"channels":1,"bit_depth":16}}}
# 后续的数据包(header.status=1, payload.audio.status=1)
{"header":{"app_id":"879230fc","uid":"thzhang4","status":1,"stmid":"0","scene":"ai-personality-1"},"payload":{"audio":{"status":1,"audio":"base64的音频数据","encoding":"raw","sample_rate":16000,"channels":1,"bit_depth":16}}}
...
{"header":{"app_id":"879230fc","uid":"thzhang4","status":1,"stmid":"0","scene":"ai-personality-1"},"payload":{"audio":{"status":1,"audio":"base64的音频数据","encoding":"raw","sample_rate":16000,"channels":1,"bit_depth":16}}}
...
# 即使用户没有说话，也要一直发送音频
...
# 结束会话 (header.status=2, payload.audio.status=2)
{"header":{"app_id":"879230fc","uid":"thzhang4","status":2,"stmid":"0","scene":"ai-personality-1"},"payload":{"audio":{"status":2,"audio":"base64的音频数据","encoding":"raw","sample_rate":16000,"channels":1,"bit_depth":16}}}

# 关闭ws连接
ws.close()
*/

// 发送任务，从录制发送队列中取出数据进行发送
void websocket_send_task(void *pvParameters)
{
    printf("发送任务成功创建");
    // 发送第一个数据包status=0，stmid=0
    char *first_packet = "{\"header\":{\"app_id\":\"734acef1\",\"uid\":\"thzhang4\",\"status\":0,\"stmid\":\"0\",\"scene\":\"sos_app\",\"interact_mode\":\"continuous\"},\"parameter\":{\"iat\":{\"iat\":{\"encoding\":\"utf8\",\"compress\":\"raw\",\"format\":\"json\"},\"vgap\":50},\"nlp\":{\"nlp\":{\"encoding\":\"utf8\",\"compress\":\"raw\",\"format\":\"json\"},\"new_session\":\"true\",\"personal\":\"人设 id\",\"prompt\":\"你是一个可爱粘人聪明机灵的台湾女孩\"},\"tts\":{\"vcn\":\"x5_lingfeiyi_flow\",\"res_id\":\"xxxx\",\"res_gender\":\"\",\"speed\":50,\"volume\":50,\"pitch\":50,\"tts\":{\"encoding\":\"raw\",\"sample_rate\":16000,\"channels\":1,\"bit_depth\":16,\"frame_size\":0}}},\"payload\":{\"audio\":{\"status\":0,\"audio\":\"\",\"encoding\":\"raw\",\"sample_rate\":16000,\"channels\":1,\"bit_depth\":16,\"frame_size\":0}}}";
    
    esp_websocket_client_send_bin(
        client, 
        first_packet, 
        strlen(first_packet), 
        1000
    );

    char second_packet[JSON_BUF_SIZE+300];
    audio_json_t audio_json;
    while (1) {
        // 1.从录制发送队列中取出数据
        if (xQueueReceive(g_audio_queue, &audio_json, portMAX_DELAY) != pdPASS) {
            continue;
        }

        // 2.组装数据,后续数据status=1，stmid=0
        sprintf(
            second_packet, 
            "{\"header\":{\"app_id\":\"734acef1\",\"uid\":\"thzhang4\",\"status\":1,\"stmid\":\"0\",\"scene\":\"sos_app\",\"interact_mode\":\"continuous\"},\"payload\":{\"audio\":{\"status\":1,\"audio\":\"%s\",\"encoding\":\"raw\",\"sample_rate\":16000,\"channels\":1,\"bit_depth\":16,\"frame_size\":%d}}}", 
            audio_json.base64_str,
            AUDIO_FRAME_SIZE
        );

        // 3.发送数据
        esp_websocket_client_send_bin(client, second_packet, strlen(second_packet), 1000);

        printf("录音数据发送成功: %s 字节\n", second_packet);
    }
}

static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
    switch (event_id) {
        case WEBSOCKET_EVENT_CONNECTED:
            ESP_LOGI("WS", "连接成功");
            vTaskDelay(4000 / portTICK_PERIOD_MS); // 延迟2秒再启动任务
            // 创建录制任务core_0、发送任务core_0、播放任务core_1
            xTaskCreatePinnedToCore(record_task, "record_task", 10240, NULL, 4, NULL, 0);
            xTaskCreatePinnedToCore(websocket_send_task, "websocket_send_task", 10240, NULL, 4, NULL, 0);
            xTaskCreatePinnedToCore(play_task, "play_task", 10240, NULL, 4, NULL, 1);
            break;
        case WEBSOCKET_EVENT_DATA:
            ESP_LOGI("WS", "收到数据: %s", (char*)data->data_ptr);
            // 处理讯飞服务器返回的消息
            if (data->data_ptr == NULL || data->data_len == 0) {
                ESP_LOGE(TAG, "收到的JSON字符串为空");
                return;
            }

            // 1. 解析整个JSON
            cJSON *root = cJSON_Parse(data->data_ptr);
            if (root == NULL) {
                // 打印解析错误信息
                const char *error_ptr = cJSON_GetErrorPtr();
                if (error_ptr != NULL) {
                    ESP_LOGE(TAG, "JSON解析错误: %s", error_ptr);
                }
                return;
            }

            data_json_t data_json;
            // 2. 提取header对象
            cJSON *header = cJSON_GetObjectItemCaseSensitive(root, "header");
            if (cJSON_IsObject(header)) {
                // 可以从header中进一步提取字段（示例）
                cJSON *code = cJSON_GetObjectItemCaseSensitive(header, "code");
                if (cJSON_IsNumber(code)) {
                    // ESP_LOGI(TAG, "header.code: %d", code->valueint);
                    switch (code->valueint)
                    {
                    case 0:
                        // 再继续提取paload，如果paload字段存在的话，取出paload.tts.audio和paload.tts.frame_size分别放入data_json
                        cJSON *payload = cJSON_GetObjectItemCaseSensitive(root, "payload");
                        if (cJSON_IsObject(payload)) {
                            cJSON *tts = cJSON_GetObjectItemCaseSensitive(payload, "tts");
                            if (cJSON_IsObject(tts)) {
                                cJSON *audio = cJSON_GetObjectItemCaseSensitive(tts, "audio");
                                if (cJSON_IsString(audio)) {
                                    ESP_LOGI(TAG, "payload.tts.frame_size: %s", audio->valuestring);
                                    data_json.json_len = strlen(audio->valuestring);
                                    memcpy(data_json.json_str, audio->valuestring, data_json.json_len);
                                    // 将信息放入队列
                                    xQueueSend(g_data_queue, &data_json, portMAX_DELAY); // 发送到队列
                                }
                            }
                        }
                        break;
                    default:
                        ESP_LOGE("响应报错", "错误码: %d", code->valueint);
                        // 断开socket
                        esp_websocket_client_close(client, 1000);
                        break;
                    }
                }
            } else {
                ESP_LOGE(TAG, "未找到有效的header对象");
                return;
            }


            break;
        case WEBSOCKET_EVENT_DISCONNECTED:
            ESP_LOGI("WS", "连接断开，尝试重连...");
            break;
    }
}


void websocket_init(void)
{
    esp_websocket_client_config_t ws_cfg = {
        .uri = generate_auth_url(),                  // 使用带鉴权参数的URL
        .transport = WEBSOCKET_TRANSPORT_OVER_SSL,   // 明确使用SSL传输
        .skip_cert_common_name_check = true,         // 跳过域名检查
        .use_global_ca_store = false,                // 不使用全局CA存储
        .cert_pem = NULL,                            // 不提供CA证书（关键：不验证服务器证书）
        .crt_bundle_attach = NULL,                   // 不使用证书bundle
        .reconnect_timeout_ms = 10000,               // 重连超时设置
    };

    client = esp_websocket_client_init(&ws_cfg);
    esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, NULL);
    esp_websocket_client_start(client);
}

// 配置I2S为全双工模式
esp_err_t i2s_init_full_duplex(void) {
    esp_err_t ret_val = ESP_OK;

    i2s_config_t i2s_cfg = {
        .mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX,  // 关键：同时启用TX（播放）和RX（录音）
        .sample_rate = 16000,                                 // 录音/播放采样率需一致（如16kHz）
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,         // 16位数据（与ES8388配置匹配）
        .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,          // 单声道（麦克风/功放都用左声道）
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
        .dma_buf_count = 8,                                   // DMA缓冲区数量（双工需足够大，避免溢出）
        .dma_buf_len = 512,                                  // 每个缓冲区大小（字节）
        .tx_desc_auto_clear = true,                           // 自动清理TX描述符（避免播放卡顿）
        .use_apll = false,
    };

    // 绑定I2S引脚（需与原理图匹配，示例引脚参考你的开发板）
    i2s_pin_config_t pin_config = {
        .bck_io_num = I2S_BCK_IO,
        .ws_io_num = I2S_WS_IO,
        .data_out_num = I2S_DO_IO,
        .data_in_num = I2S_DI_IO,
        .mck_io_num = IS2_MCLK_IO,
    };

    // 安装I2S驱动（注意：全双工模式需指定“0”个事件队列，由任务主动读写）
    ret_val |= i2s_driver_install(I2S_NUM_0, &i2s_cfg, 0, NULL);
    ret_val |= i2s_set_pin(I2S_NUM_0, &pin_config);
    ret_val |= i2s_zero_dma_buffer(I2S_NUM);
    return ret_val;
}

/**
 * @brief       ES8388进入录音+播放模式
 * @param       无
 * @retval      0,初始化正常
 *              其他,错误代码
 */
void es8388_enter_full_duplex_mode(i2c_obj_t self)
{
    es8388_adda_cfg(1, 1);          /* 开启ADC和DAC */
    es8388_input_cfg(0);            /* 开启输入通道(通道1,MIC所在通道) */
    es8388_mic_gain(8);             /* MIC增益设置为最大 */
    es8388_alc_ctrl(3, 4, 4);       /* 开启立体声ALC控制,以提高录音音量 */
    es8388_output_cfg(1, 1);        /* 开启通道2的输出，通道1是耳机，通道2是喇叭 */
    es8388_spkvol_set(25);          /* 喇叭音量设置 */
    i2s_set_samplerate_bits_sample(SAMPLE_RATE,I2S_BITS_PER_SAMPLE_16BIT);    /* 初始化I2S */
    i2s_trx_start();                /* 开启I2S */
}

/**
 * @brief 播放PCM音频数据
 * @param pcm_buf: 解码后的PCM数据缓冲区
 * @param pcm_len: 数据长度（字节）
 */
void es8388_play(uint8_t *pcm_buf, uint32_t pcm_len) {
    if (pcm_buf == NULL || pcm_len == 0) {
        return; // 无效参数
    }

    // 初始化播放硬件（首次调用时执行）
    // static uint8_t is_initialized = 0;
    // if (!is_initialized) {
    //     es8388_play_init();
    //     is_initialized = 1;
    // }

    // 分块发送数据（避免单次发送过大导致阻塞）
    uint32_t offset = 0;
    uint32_t chunk_size = 1024;     // 每次发送1024字节
    while (offset < pcm_len) {
        uint32_t send_len = (pcm_len - offset) > chunk_size ? chunk_size : (pcm_len - offset);
        
        // 通过I2S发送PCM数据
        i2s_tx_write(pcm_buf + offset, send_len);
        
        offset += send_len;
        vTaskDelay(1 / portTICK_PERIOD_MS); // 匹配播放速度，避免缓冲区溢出
    }
}

/**
 * @brief 录制任务：采集音频→Base64编码→封装JSON→发送到队列
 */
static void record_task(void *arg) {
    uint8_t pcm_buf[AUDIO_FRAME_SIZE];         // 原始PCM音频缓冲区
    audio_json_t audio_json;                   // 待发送到队列的JSON数据

    while (1) {
        // 1. 获取I2S硬件访问权
        if (xSemaphoreTake(g_i2s_sem, portMAX_DELAY) != pdPASS) {
            ESP_LOGE("RECORD", "获取I2S信号量失败");
            vTaskDelay(10 / portTICK_PERIOD_MS);
            continue;
        }

        // 2. 录制一帧PCM数据（调用现有录音接口，根据实际函数调整）
        size_t rec_len = i2s_rx_read(pcm_buf, AUDIO_FRAME_SIZE);  // 假设recorder_read是采集函数
        xSemaphoreGive(g_i2s_sem);  // 释放I2S硬件
        //printf("录制数据长度: %d\n", rec_len);

        if (rec_len <= 0) {
            ESP_LOGE("RECORD", "录制失败");
            vTaskDelay(10 / portTICK_PERIOD_MS);
            continue;
        }

        // 3. 将PCM数据Base64编码插入队列结构体
        size_t base64_len;
        int ret = mbedtls_base64_encode(
            audio_json.base64_str, sizeof(audio_json.base64_str), &audio_json.json_len,
            pcm_buf, rec_len
        );
        if (ret != 0) {
            ESP_LOGE("RECORD", "Base64编码失败: %d", ret);
            vTaskDelay(10 / portTICK_PERIOD_MS);
            continue;
        }

        // 5. 发送到队列（非阻塞，队列满则丢弃）
        if (xQueueSend(g_audio_queue, &audio_json, 0) != pdPASS) {
            ESP_LOGE("RECORD", "队列满，丢弃数据");
        }

        // 控制录制频率（根据采样率调整，例如16K采样率、16位深、单声道：16000*2=32000字节/秒 → 约32ms一帧）
        vTaskDelay(40 / portTICK_PERIOD_MS);
        // printf("录制完成\n");

    }
}

/**
 * @brief 播放任务：从队列接收JSON→解析Base64→解码→播放
 */
static void play_task(void *arg) {
    data_json_t data_json;                   // 从队列接收的JSON数据
    uint8_t pcm_buf[AUDIO_FRAME_SIZE];         // 解码后的PCM数据

    while (1) {
        // 1. 从队列阻塞读取数据（等待新数据）
        if (xQueueReceive(g_data_queue, &data_json, portMAX_DELAY) != pdPASS) {
            continue;  // 理论上不会触发，因为portMAX_DELAY会一直等
        }

        // 3. Base64解码得到原始PCM数据
        size_t pcm_len;
        int ret = mbedtls_base64_decode(
            pcm_buf, sizeof(pcm_buf), &pcm_len,
            data_json.json_str, data_json.json_len
        );
        if (ret != 0) {
            ESP_LOGE("PLAY", "Base64解码失败: %d", ret);
            continue;
        }
        printf("播放数据长度: %d\n", pcm_len);
        
        // 4. 获取I2S硬件访问权并播放
        if (xSemaphoreTake(g_i2s_sem, portMAX_DELAY) != pdPASS) {
            ESP_LOGE("PLAY", "获取I2S信号量失败");
            continue;
        }

        // 调用播放接口输出PCM数据（根据实际函数调整）
        es8388_play(pcm_buf, pcm_len);  // 假设es8388_play是播放函数
        xSemaphoreGive(g_i2s_sem);  // 释放I2S硬件

        printf("播放完成\n");

    }
}

/**
 * @brief       程序入口
 * @param       无
 * @retval      无
 */
void app_main(void)
{
    uint8_t key;
    esp_err_t ret;

    ret = nvs_flash_init();             /* 初始化NVS */

    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }

    i2c0_master = iic_init(I2C_NUM_0); /* 初始化IIC0 */
    xl9555_init(i2c0_master);          /* 初始化XL9555 */
    es8388_init(i2c0_master);       /* ES8388初始化 */
    ESP_ERROR_CHECK(i2s_init_full_duplex()); /* I2S全双工初始化 */
    wifi_sta_init();    // 初始化wifi
    ntp_init();    // 确保时间同步完成
    // ESP_ERROR_CHECK(app_sr_start());    // 初始化语音识别
    websocket_init();
    printf("所有初始化完成!\n");

    /*************************************************************************/

    xl9555_pin_write(SPK_EN_IO, 0); /* 打开喇叭 */
    // 初始化队列和信号量
    g_audio_queue = xQueueCreate(QUEUE_LEN, sizeof(audio_json_t));
    g_data_queue = xQueueCreate(QUEUE_LEN, sizeof(data_json_t));
    g_i2s_sem = xSemaphoreCreateBinary();
    xSemaphoreGive(g_i2s_sem);  // 初始释放信号量

    if (g_audio_queue == NULL || g_i2s_sem == NULL) {
        ESP_LOGE("MAIN", "队列或信号量创建失败");
        return;
    }

    es8388_enter_full_duplex_mode(i2c0_master);

    // 启动任务（播放任务优先级高于录制，避免卡顿）
    // xTaskCreatePinnedToCore(record_task, "record_task", 8192, NULL, 4, NULL, 0);
    // xTaskCreatePinnedToCore(play_task, "play_task", 8192, NULL, 5, NULL, 1);


    while(1){
        vTaskDelay(10);
    }
}